package jaux.tank.generator.component;

import java.awt.BorderLayout;
import java.awt.GridLayout;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.WindowConstants;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;

import jaux.tank.generator.MainApp;
import jaux.tank.generator.domain.TankEntity;
import jaux.tank.generator.domain.TankEntityField;
import jaux.tank.generator.domain.TankEntityRelationship;
import jaux.tank.generator.domain.TankEntityRelationship.Pointer;
import jaux.tank.generator.domain.TankEntityRelationship.PointerCascade;
import jaux.tank.generator.domain.TankEntityRelationship.RelationshipType;
import jaux.tank.generator.domain.TankRoot;
import jaux.tank.generator.jd.components.JdCheckBox;
import jaux.tank.generator.jd.components.JdCheckBoxGroup;
import jaux.tank.generator.jd.components.JdComboBox;
import jaux.tank.generator.jd.components.JdLabeledInput;
import jaux.tank.generator.jd.components.JdTextField;
import jaux.tank.generator.jd.layout.JdGridLayout;
import jaux.tank.generator.util.TextUtil;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class RelationshipEditorPanel extends JPanel{

	private static final long serialVersionUID = 1L;
	
	@Getter
	private transient TankEntityRelationship relationship;
	JdComboBox<String> fromEntityComboBox ;
	JdComboBox<String> fromDisplayFieldComboBox;
	
	JdComboBox<String> toEntityComboBox ;
	JdComboBox<String> toDisplayFieldComboBox;
	
	transient Supplier<List<TankEntity>> entitiesSupplier;
	@Getter
	@Setter
	private boolean saved;

	
	public RelationshipEditorPanel(TankEntityRelationship relationship, Supplier<List<TankEntity>> entitiesSupplier ) {
		this.relationship = relationship;
		this.entitiesSupplier = entitiesSupplier;
		initializeComponents();
	}
	
	private void initializeComponents() {

		setLayout(new BorderLayout(0, 0));
		List<JdLabeledInput<?, ?>> inputs = new ArrayList<>();
		// name and type
		JPanel basicPanel = new JPanel();
		basicPanel.setLayout(new GridLayout(0, 2));
		add(basicPanel, BorderLayout.NORTH);
		JdTextField nameTextField = new JdTextField("Name:", relationship.getName());
		nameTextField.setEditable(false);
		basicPanel.add(nameTextField);
		inputs.add(nameTextField);
		JdComboBox<RelationshipType> typeComboBox = new JdComboBox<>("Type:", relationship.getType(), RelationshipType.values());
		typeComboBox.onChange(value ->{ 
			relationship.setType(value);
			this.saved = false;
		});
		basicPanel.add(typeComboBox);
		inputs.add(typeComboBox);
		// from and to
		JPanel fromToPanel = new JPanel();
		fromToPanel.setLayout(new GridLayout(0, 2));
		add(fromToPanel, BorderLayout.CENTER);
		
		// from
		JPanel fromPanel = new JPanel();
		fromPanel.setLayout(new JdGridLayout(0, 1));
		fromPanel.setBorder(BorderFactory.createTitledBorder("From Entity"));
		fromToPanel.add(fromPanel);
		List<TankEntity> entities = entitiesSupplier.get();
		String[] entityNames = entities==null?new String[0]:entities.stream().map(TankEntity::getName).toArray(String[]::new);
		Pointer fromPointer = relationship.getFrom();
		fromEntityComboBox = new JdComboBox<>("From:", fromPointer.getEntity(), entityNames);
		fromEntityComboBox.setToolTipText(TextUtil.getInstance().getString("message.tooltip.relationship.fromEntity"));
		fromEntityComboBox.addMouseListener(new MouseAdapter() {
			@Override
			public void mouseEntered(MouseEvent e) {
				updateFromToEntities();
				super.mouseEntered(e);
			}
		});
		fromPanel.add(fromEntityComboBox);
		inputs.add(fromEntityComboBox);
		
		String fromEntityName = fromPointer.getEntity();
		TankEntity fromEntity = entities == null ? null :entities.stream().filter(entity->entity.getName().equals(fromEntityName)).findFirst().orElse(null);
		String fromEntityDisplayField = fromPointer.getDisplayField();
		String[] fromEntityFields = fromEntity==null?new String[0]:fromEntity.getFields().stream().map(TankEntityField::getName).toArray(String[]::new);
		fromDisplayFieldComboBox = new JdComboBox<>("Display Field:", fromEntityDisplayField, fromEntityFields);
		fromDisplayFieldComboBox.setToolTipText(TextUtil.getInstance().getString("message.tooltip.relationship.fromEntityDisplayField"));
		fromDisplayFieldComboBox.onChange(value -> relationship.getFrom().setDisplayField(value));
		fromDisplayFieldComboBox.addMouseListener(new MouseAdapter() {
			@Override
			public void mouseEntered(MouseEvent e) {
				updateFromToEntities();
				super.mouseEntered(e);
			}
		});
		fromPanel.add(fromDisplayFieldComboBox);
		inputs.add(fromDisplayFieldComboBox);
		
		JdTextField fromRelationNameTextField = new JdTextField("Name:", fromPointer.getRelationName());
		fromRelationNameTextField.setToolTipText(TextUtil.getInstance().getString("message.tooltip.relationship.fromEntityRelationshipName"));
		fromRelationNameTextField.onChange(value -> relationship.getFrom().setRelationName(value));
		fromPanel.add(fromRelationNameTextField);
		inputs.add(fromRelationNameTextField);
		
		JdTextField fromRelationCommentTextField = new JdTextField("Comment:", fromPointer.getComment());
		fromRelationCommentTextField.setToolTipText(TextUtil.getInstance().getString("message.tooltip.relationship.fromEntityRelationshipComment"));
		fromRelationCommentTextField.onChange(value -> relationship.getFrom().setComment(value));
		fromPanel.add(fromRelationCommentTextField);
		inputs.add(fromRelationCommentTextField);
		
		JdCheckBox fromRequiredCheckBox = new JdCheckBox("Required:", fromPointer.isRequired());
		fromRequiredCheckBox.setToolTipText(TextUtil.getInstance().getString("message.tooltip.relationship.fromEntityRelationshipRequired"));
		fromRequiredCheckBox.onChange(value -> relationship.getFrom().setRequired(value));
		fromPanel.add(fromRequiredCheckBox);
		inputs.add(fromRequiredCheckBox);
		// cascades
		JdCheckBoxGroup<PointerCascade> fromCascade = new JdCheckBoxGroup<>("Cascade:", fromPointer.getCascades(), Arrays.asList(PointerCascade.values()));
		fromCascade.setInputComponentSize(200, 100);
		fromCascade.setToolTipText(TextUtil.getInstance().getString("message.tooltip.relationship.fromEntityCascade"));
		fromCascade.onChange(value -> {
			relationship.getFrom().setCascades(value);
		});
		fromPanel.add(fromCascade);
		inputs.add(fromCascade);
		
		// built in
		JdCheckBox fromBuiltInCheckBox = new JdCheckBox("Build in Entity:", fromPointer.isBuiltInEntity());
		fromBuiltInCheckBox.setToolTipText(TextUtil.getInstance().getString("message.tooltip.relationship.fromEntityBuiltIn"));
		fromBuiltInCheckBox.onChange(value ->{ 
			relationship.getFrom().setBuiltInEntity(value);
		});
		fromPanel.add(fromBuiltInCheckBox);
		inputs.add(fromBuiltInCheckBox);
		
		
		// to
		JPanel toPanel = new JPanel();
		toPanel.setLayout(new JdGridLayout(0, 1));
		toPanel.setBorder(BorderFactory.createTitledBorder("To Entity"));
		fromToPanel.add(toPanel);
		
		Pointer toPointer = relationship.getTo();
		String toEntityName = toPointer.getEntity();
		TankEntity toEntity = entities == null?null:entities.stream().filter(entity->entity.getName().equals(toEntityName)).findFirst().orElse(null);
		toEntityComboBox = new JdComboBox<>("To:", toEntityName,entityNames);
		toEntityComboBox.setToolTipText(TextUtil.getInstance().getString("message.tooltip.relationship.toEntity"));
		toEntityComboBox.addMouseListener(new MouseAdapter() {
			@Override
			public void mouseEntered(MouseEvent e) {
				updateFromToEntities();
				super.mouseEntered(e);
			}
		});
		toPanel.add(toEntityComboBox);
		inputs.add(toEntityComboBox);
		String[] toEntityFields = toEntity==null?new String[0]:toEntity.getFields().stream().map(TankEntityField::getName).toArray(String[]::new);
		toDisplayFieldComboBox = new JdComboBox<>("Display Field:", toPointer.getDisplayField(),toEntityFields);
		toDisplayFieldComboBox.setToolTipText(TextUtil.getInstance().getString("message.tooltip.relationship.toEntityDisplayField"));
		toDisplayFieldComboBox.onChange(value -> relationship.getTo().setDisplayField(value));
		toDisplayFieldComboBox.addMouseListener(new MouseAdapter() {
			@Override
			public void mouseEntered(MouseEvent e) {
				updateFromToEntities();
				super.mouseEntered(e);
			}
		});
		toPanel.add(toDisplayFieldComboBox);
		inputs.add(toDisplayFieldComboBox);
		
		JdTextField toRelationNameTextField = new JdTextField("Name:", toPointer.getRelationName());
		toRelationNameTextField.setToolTipText(TextUtil.getInstance().getString("message.tooltip.relationship.toEntityRelationshipName"));
		toRelationNameTextField.onChange(value -> relationship.getTo().setRelationName(value));
		toPanel.add(toRelationNameTextField);
		inputs.add(toRelationNameTextField);
		
		
		JdTextField toRelationCommentTextField = new JdTextField("Comment:", toPointer.getComment());
		toRelationCommentTextField.setToolTipText(TextUtil.getInstance().getString("message.tooltip.relationship.toEntityRelationshipComment"));
		toRelationCommentTextField.onChange(value -> relationship.getTo().setComment(value));
		toPanel.add(toRelationCommentTextField);
		inputs.add(toRelationCommentTextField);
		
		JdCheckBox toRequiredCheckBox = new JdCheckBox("Required:", toPointer.isRequired());
		toRequiredCheckBox.setToolTipText(TextUtil.getInstance().getString("message.tooltip.relationship.toEntityRelationshipRequired"));
		toRequiredCheckBox.onChange(value -> relationship.getTo().setRequired(value));
		toPanel.add(toRequiredCheckBox);
		inputs.add(toRequiredCheckBox);
		
		// cascades
		JdCheckBoxGroup<PointerCascade> toCascade = new JdCheckBoxGroup<>("Cascade:",toPointer.getCascades(), Arrays.asList(PointerCascade.values()));
		toCascade.setInputComponentSize(200, 100);
		toCascade.setToolTipText(TextUtil.getInstance().getString("message.tooltip.relationship.toEntityCascade"));
		toCascade.onChange(value -> {
			relationship.getTo().setCascades(value);
		});
		toPanel.add(toCascade);
		inputs.add(toCascade);
		
		JdCheckBox toBuiltInCheckBox = new JdCheckBox("Built in Entity:", toPointer.isBuiltInEntity());
		toBuiltInCheckBox.setToolTipText(TextUtil.getInstance().getString("message.tooltip.relationship.toEntityBuiltIn"));
		toBuiltInCheckBox.onChange(value ->{ 
			relationship.getTo().setBuiltInEntity(value);
			if(Boolean.TRUE.equals(value)) {
				fromCascade.setValue(null);
				fromCascade.setEditable(false);
			}else {
				fromCascade.setEditable(true);
				
			}
		});
		toPanel.add(toBuiltInCheckBox);
		inputs.add(toBuiltInCheckBox);
		
		fromBuiltInCheckBox.onChange(value ->{ 
			relationship.getFrom().setBuiltInEntity(value);
			if(Boolean.TRUE.equals(value)) {
				toCascade.setValue(null);
				toCascade.setEditable(false);
			}else {
				toCascade.setEditable(true);
			}
		});
		
		// comment
		JdTextField commentTextField = new JdTextField("Comment:", relationship.getComment());
		commentTextField.setToolTipText("the comment of the relationship");
		commentTextField.onChange(value -> relationship.setComment(value));
		commentTextField.setInputComponentSize(300, 0);
		add(commentTextField, BorderLayout.SOUTH);
		inputs.add(commentTextField);
		
		// set value
		inputs.forEach(input -> input.onChange(v->this.saved= false));
		
		updateFromToEntities();
	}
	

	private void updateFromToEntities() {
		log.debug("update from to entity");
		// from
		updateFromEntity();
		// to
		updateToEntity();
		
	}

	private void updateFromEntity() {
		// get entities
		boolean fromBuiltInEntity = relationship.getFrom().isBuiltInEntity();
		List<TankEntity> entities = new ArrayList<>();
		if(fromBuiltInEntity) {
			try(InputStream is = ClassLoader.getSystemResourceAsStream("builtInEntities.json")){
				
				entities.addAll(new ObjectMapper().readValue(is,new TypeReference<List<TankEntity>>(){}));
			} catch (IOException e) {
				log.error("get built in entities failed",e);
			}
		}else {
			entities.addAll(entitiesSupplier.get());
		}
		// clear entity and field if no entities,then return
		fromEntityComboBox.clearChangeHandlers();
		fromEntityComboBox.onChange(value->{
			this.saved= false;
			relationship.getFrom().setEntity(value);
			fromDisplayFieldComboBox.setListData(new String[0]);
			entities.stream()
			.filter(entity->entity.getName().equals(value))
			.findFirst()
			.ifPresent(entity->{
				fromDisplayFieldComboBox.setListData(entity.getFields().stream().map(TankEntityField::getName).toArray(String[]::new));
			});
		});
		if( entities.isEmpty()) {
			fromEntityComboBox.setListData(new String[0]);
			fromDisplayFieldComboBox.setListData(new String[0]);
			return;
		}
		String[] entityNames =  entities.stream().map(TankEntity::getName).toArray(String[]::new) ;
		log.debug("update from to entity: {}", String.join(",", entityNames));
		// from
		String oldFromEntityName = fromEntityComboBox.setListData(entityNames);
		log.debug("old from entity: {}", oldFromEntityName);
		if(oldFromEntityName!=null) {
			TankEntity oldEntity = entities.stream().filter(entity->entity.getName().equals(oldFromEntityName)).findFirst().orElse(null);
			if(oldEntity!=null) {
				fromDisplayFieldComboBox.setListData(oldEntity.getFields().stream().map(TankEntityField::getName).toArray(String[]::new));
			}
		}

	}
	
	private void updateToEntity() {
		// get entities
		boolean toBuiltInEntity = relationship.getTo().isBuiltInEntity();
		List<TankEntity> entities = new ArrayList<>();
		if(toBuiltInEntity) {
			try(InputStream is = ClassLoader.getSystemResourceAsStream("builtInEntities.json")){
				
				entities.addAll(new ObjectMapper().readValue(is,new TypeReference<List<TankEntity>>(){}));
			} catch (IOException e) {
				log.error("get built in entities failed",e);
			}
		}else {
			entities.addAll(entitiesSupplier.get());
		}
		// clear entity and field if no entities,then return
		toEntityComboBox.clearChangeHandlers();
		toEntityComboBox.onChange(value->{
			this.saved= false;
			relationship.getTo().setEntity(value);
			toDisplayFieldComboBox.setListData(new String[0]);
			entities.stream()
			.filter(entity->entity.getName().equals(value))
			.findFirst()
			.ifPresent(entity->{
				toDisplayFieldComboBox.setListData(entity.getFields().stream().map(TankEntityField::getName).toArray(String[]::new));
			});
		});
		if( entities.isEmpty()) {
			toEntityComboBox.setListData(new String[0]);
			toDisplayFieldComboBox.setListData(new String[0]);
			return;
		}
		String[] entityNames =  entities.stream().map(TankEntity::getName).toArray(String[]::new) ;
		log.debug("update  to entity: {}", String.join(",", entityNames));
		// from
		String oldToEntityName = toEntityComboBox.setListData(entityNames);
		if(oldToEntityName!=null) {
			TankEntity oldEntity = entities.stream().filter(entity->entity.getName().equals(oldToEntityName)).findFirst().orElse(null);
			if(oldEntity!=null) {
				toDisplayFieldComboBox.setListData(oldEntity.getFields().stream().map(TankEntityField::getName).toArray(String[]::new));
			}
		}
	}
	
	public static void main(String[] args) {
		// test
		JFrame frame = new JFrame();
		frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
		TankEntity entity = new TankEntity();
		entity.setName("test");
		TankEntityRelationship relationship = new TankEntityRelationship();
		TankRoot root = new TankRoot();
		RelationshipEditorPanel relationshipEditorPanel = new RelationshipEditorPanel(relationship, ()->root.getEntities());
		frame.getContentPane().add(relationshipEditorPanel, BorderLayout.CENTER);
		// get value button
		JButton getValueButton = new JButton();
		getValueButton.setText("get value");
		getValueButton.addActionListener(e -> {
			JOptionPane.showMessageDialog(frame, relationshipEditorPanel.getRelationship());
		});
		frame.getContentPane().add(getValueButton, BorderLayout.SOUTH);
		frame.pack();
		frame.setVisible(true);
	}

}
